package com.thouiot.modbus.util;

import com.serotonin.modbus4j.base.ModbusUtils;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

/**
 * @description modbus 数据协议转换工具类
 * @author wangkai
 * @date 2025/04/15 14:30
 */
public class ModbusConvertUtil {

    public static byte[] convertToBytes(boolean[] bdata) {
        int byteCount = (bdata.length + 7) / 8;
        byte[] data = new byte[byteCount];

        for(int i = 0; i < bdata.length; ++i) {
            data[i / 8] = (byte)(data[i / 8] | (bdata[i] ? 1 : 0) << i % 8);
        }

        return data;
    }

    public static byte[] convertToBytes(short[] sdata) {
        int byteCount = sdata.length * 2;
        byte[] data = new byte[byteCount];

        for(int i = 0; i < sdata.length; ++i) {
            data[i * 2] = (byte)(255 & sdata[i] >> 8);
            data[i * 2 + 1] = (byte)(255 & sdata[i]);
        }

        return data;
    }

    public static boolean[] convertToBooleans(byte[] data) {
        boolean[] bdata = new boolean[data.length * 8];

        for(int i = 0; i < bdata.length; ++i) {
            bdata[i] = (data[i / 8] >> i % 8 & 1) == 1;
        }

        return bdata;
    }

    public static short[] convertToShorts(byte[] data) {
        short[] sdata = new short[data.length / 2];

        for(int i = 0; i < sdata.length; ++i) {
            sdata[i] = ModbusUtils.toShort(data[i * 2], data[i * 2 + 1]);
        }

        return sdata;
    }


    // 将List<包装类>转换为 基本类型数组
    public static boolean[] convertToBooleans(List<Boolean> list) {
        boolean[] array = new boolean[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i);
        }
        return array;
    }

//    public static short[] convertToShorts(List<Short> list) {
//        short[] array = new short[list.size()];
//        for (int i = 0; i < list.size(); i++) {
//            array[i] = list.get(i);
//        }
//        return array;
//    }

    public static short[] convertToShorts(List<Integer> list) {
        short[] array = new short[list.size()];
        for (int i = 0; i < list.size(); i++) {
            array[i] = list.get(i).shortValue();
        }
        return array;
    }

    public static List<Number> convertToNumbers(byte[] bytes, int dataType) {
        int registerCount = getRegisterCount(dataType);
        int count = bytes.length / (registerCount * 2);
        List<Number> result = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            // 这里是 i * registerCount，而不是 i * registerCount * 2，因为modbus4j 内部的offset 都是寄存器位偏移，即以 short 为单位的偏移
            Number number = bytesToValueRealOffset(bytes, i * registerCount, dataType);
            result.add(number);
        }
        return result;
    }


    /**
     * 根据dataType 获取类型占几个寄存器位，即几个 short
     * @param dataType
     * @return
     */
    public static int getRegisterCount(int dataType) {
        switch (dataType) {
            case 2:
            case 3:
            case 16:
            case 22:
            case 23:
                return 1;
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
            case 17:
            case 20:
            case 24:
            case 25:
                return 2;
            case 10:
            case 11:
            case 12:
            case 13:
            case 14:
            case 15:
                return 4;
            case 18:
            case 19:
            case 21:
            default:
                throw new RuntimeException("Unsupported data type: " + dataType);
        }
    }

    /**
     *
     * @param data          字节数组
     * @param offset        寄存器位偏移，modbus4j中都是以modbus 位做偏移，即以 short 为单位的偏移，这里保持一致
     * @param dataType      数据类型
     * @return
     */
    public static Number bytesToValueRealOffset(byte[] data, int offset, int dataType) {
        offset = offset * 2;
        if (dataType == 2) {
            return new Integer((data[offset] & 255) << 8 | data[offset + 1] & 255);
        } else if (dataType == 3) {
            return new Short((short)((data[offset] & 255) << 8 | data[offset + 1] & 255));
        } else if (dataType == 22) {
            return new Integer((data[offset + 1] & 255) << 8 | data[offset] & 255);
        } else if (dataType == 23) {
            return new Short((short)((data[offset + 1] & 255) << 8 | data[offset] & 255));
        } else {
            StringBuilder sb;
            if (dataType == 16) {
                sb = new StringBuilder();
                appendBCD(sb, data[offset]);
                appendBCD(sb, data[offset + 1]);
                return Short.parseShort(sb.toString());
            } else if (dataType == 4) {
                return new Long((long)(data[offset] & 255) << 24 | (long)(data[offset + 1] & 255) << 16 | (long)(data[offset + 2] & 255) << 8 | (long)(data[offset + 3] & 255));
            } else if (dataType == 5) {
                return new Integer((data[offset] & 255) << 24 | (data[offset + 1] & 255) << 16 | (data[offset + 2] & 255) << 8 | data[offset + 3] & 255);
            } else if (dataType == 6) {
                return new Long((long)(data[offset + 2] & 255) << 24 | (long)(data[offset + 3] & 255) << 16 | (long)(data[offset] & 255) << 8 | (long)(data[offset + 1] & 255));
            } else if (dataType == 7) {
                return new Integer((data[offset + 2] & 255) << 24 | (data[offset + 3] & 255) << 16 | (data[offset] & 255) << 8 | data[offset + 1] & 255);
            } else if (dataType == 24) {
                return new Long((long)(data[offset + 3] & 255) << 24 | (long)((data[offset + 2] & 255) << 16) | (long)(data[offset + 1] & 255) << 8 | (long)(data[offset] & 255));
            } else if (dataType == 25) {
                return new Integer((data[offset + 3] & 255) << 24 | (data[offset + 2] & 255) << 16 | (data[offset + 1] & 255) << 8 | data[offset] & 255);
            } else if (dataType == 8) {
                return Float.intBitsToFloat((data[offset] & 255) << 24 | (data[offset + 1] & 255) << 16 | (data[offset + 2] & 255) << 8 | data[offset + 3] & 255);
            } else if (dataType == 9) {
                return Float.intBitsToFloat((data[offset + 2] & 255) << 24 | (data[offset + 3] & 255) << 16 | (data[offset] & 255) << 8 | data[offset + 1] & 255);
            } else if (dataType == 27) {
                return Float.intBitsToFloat((data[offset + 1] & 255) << 24 | (data[offset] & 255) << 16 | (data[offset + 3] & 255) << 8 | data[offset + 2] & 255);
            } else if (dataType == 26) {
                return Float.intBitsToFloat((data[offset + 3] & 255) << 24 | (data[offset + 2] & 255) << 16 | (data[offset + 1] & 255) << 8 | data[offset] & 255);
            } else if (dataType == 17) {
                sb = new StringBuilder();
                appendBCD(sb, data[offset]);
                appendBCD(sb, data[offset + 1]);
                appendBCD(sb, data[offset + 2]);
                appendBCD(sb, data[offset + 3]);
                return Integer.parseInt(sb.toString());
            } else if (dataType == 20) {
                sb = new StringBuilder();
                appendBCD(sb, data[offset + 2]);
                appendBCD(sb, data[offset + 3]);
                appendBCD(sb, data[offset]);
                appendBCD(sb, data[offset + 1]);
                return Integer.parseInt(sb.toString());
            } else {
                byte[] b9;
                if (dataType == 10) {
                    b9 = new byte[9];
                    System.arraycopy(data, offset, b9, 1, 8);
                    return new BigInteger(b9);
                } else if (dataType == 11) {
                    return new Long((long)(data[offset] & 255) << 56 | (long)(data[offset + 1] & 255) << 48 | (long)(data[offset + 2] & 255) << 40 | (long)(data[offset + 3] & 255) << 32 | (long)(data[offset + 4] & 255) << 24 | (long)(data[offset + 5] & 255) << 16 | (long)(data[offset + 6] & 255) << 8 | (long)(data[offset + 7] & 255));
                } else if (dataType == 12) {
                    b9 = new byte[]{0, data[offset + 6], data[offset + 7], data[offset + 4], data[offset + 5], data[offset + 2], data[offset + 3], data[offset], data[offset + 1]};
                    return new BigInteger(b9);
                } else if (dataType == 13) {
                    return new Long((long)(data[offset + 6] & 255) << 56 | (long)(data[offset + 7] & 255) << 48 | (long)(data[offset + 4] & 255) << 40 | (long)(data[offset + 5] & 255) << 32 | (long)(data[offset + 2] & 255) << 24 | (long)(data[offset + 3] & 255) << 16 | (long)(data[offset] & 255) << 8 | (long)(data[offset + 1] & 255));
                } else if (dataType == 14) {
                    return Double.longBitsToDouble((long)(data[offset] & 255) << 56 | (long)(data[offset + 1] & 255) << 48 | (long)(data[offset + 2] & 255) << 40 | (long)(data[offset + 3] & 255) << 32 | (long)(data[offset + 4] & 255) << 24 | (long)(data[offset + 5] & 255) << 16 | (long)(data[offset + 6] & 255) << 8 | (long)(data[offset + 7] & 255));
                } else if (dataType == 15) {
                    return Double.longBitsToDouble((long)(data[offset + 6] & 255) << 56 | (long)(data[offset + 7] & 255) << 48 | (long)(data[offset + 4] & 255) << 40 | (long)(data[offset + 5] & 255) << 32 | (long)(data[offset + 2] & 255) << 24 | (long)(data[offset + 3] & 255) << 16 | (long)(data[offset] & 255) << 8 | (long)(data[offset + 1] & 255));
                } else {
                    throw new RuntimeException("Unsupported data type: " + dataType);
                }
            }
        }
    }

    private static void appendBCD(StringBuilder sb, byte b) {
        sb.append(bcdNibbleToInt(b, true));
        sb.append(bcdNibbleToInt(b, false));
    }

    private static int bcdNibbleToInt(byte b, boolean high) {
        int n;
        if (high) {
            n = b >> 4 & 15;
        } else {
            n = b & 15;
        }

        if (n > 9) {
            n = 0;
        }

        return n;
    }
}
